using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using CkSoftware.GroupMe.Sdk.Core.Dao;
using CkSoftware.GroupMe.Sdk.Core.Exceptions;
using CkSoftware.GroupMe.Sdk.Groups;
using CkSoftware.GroupMe.Sdk.Helpers.Authorization;
using CkSoftware.GroupMe.Sdk.Helpers.Emoji;
using CkSoftware.GroupMe.Sdk.Helpers.Users;
using CkSoftware.GroupMe.Sdk.Messaging;
using DotNetNuke.Services.Exceptions;

namespace CkSoftware.GroupMeChatModule.Managers
{
	public class GroupManager
	{
		private readonly int _moduleId;

		/// <summary>
		/// INTERNAL, DO NOT USE EXCEPT IN GetMessages() Method!
		/// </summary>
		private GroupMeMessages _messages;

		private bool _messagesLoaded;

		private GroupMeGroup _group;

		private readonly SemaphoreSlim _messageLoadLock = new SemaphoreSlim(1);

		private readonly SemaphoreSlim _messageProcessingLock = new SemaphoreSlim(1);

		private Timer _messageRefreshTimer;

		public readonly Lazy<GroupMeClientAuthHelper> AuthHelper = new Lazy<GroupMeClientAuthHelper>(() => new GroupMeClientAuthHelper());

		private readonly List<string> _processedMessageIds = new List<string>();

		public GroupMeAuthManager AuthManager
		{
			get { return GroupMeAuthManager.Instance; }
		}

		private int _currentSettingsHash;

		private int NewSettingsHash
		{
			get { return ModuleSettings.GetHashCode(); }
		}

		private bool IsSettingsDirty
		{
			get { return _currentSettingsHash != NewSettingsHash; }
		}

		public ModuleSettingsManager ModuleSettings { get { return new ModuleSettingsManager(_moduleId); } }

		public GroupManager(int moduleId)
		{
			_moduleId = moduleId;
		}

		public async Task<GroupMeMessages> GetMessagesObject()
		{
			if (!_messagesLoaded || IsSettingsDirty)
			{
				await _messageLoadLock.WaitAsync();

				try
				{
					_messagesLoaded = false;

					await EnsureGroupLoaded();

					if (!_messagesLoaded)
					{
						_currentSettingsHash = NewSettingsHash;
						_messages = _group.Messages;
						_messagesLoaded = true;

						if (_messageRefreshTimer != null)
						{
							_messageRefreshTimer.Dispose();
						}

						_messageRefreshTimer = new Timer(MessageRefreshTimerCallback, null, TimeSpan.FromMilliseconds(250), TimeSpan.FromSeconds(2));
					}
				}
				finally
				{
					_messageLoadLock.Release();
				}
			}

			return _messages;
		}

		private async Task EnsureGroupLoaded()
		{
			try
			{
				_group = await GetGroup();
			}
			catch (GroupMeException ex)
			{
				Exceptions.LogException(ex);
				throw;
			}
		}

		public async Task<object> GetAuthorizationStatus(int userId, string sessionId)
		{
			await EnsureGroupLoaded();
			
			var authToken = AuthManager.GetAuthTokenForUserOrAnonymous(userId, sessionId);
			var addedToGroup = false;
			if (!string.IsNullOrEmpty(authToken))
			{
				var apiAccess = AuthHelper.Value.GetWebApiAccessForAccessToken(authToken);
				var userHelper = new UserHelper(apiAccess);
				var userData = await userHelper.GetCurrentUser();
				addedToGroup = _group.Data.Members.Any(m => m.UserId == userData.UserId);
			}

			return new
			{
				IsDnnUser = userId > 0,
				IsLoggedIn = AuthManager.HaveAuthTokenForUser(userId) || AuthManager.HaveAuthTokenForAnonymous(sessionId),
				IsAddedToGroup = addedToGroup,
				GroupShareUrl = _group.Data.ShareUrl,
				StayLoggedIn = AuthManager.HavePermanentLogin(userId)
			};
		}

		private async void MessageRefreshTimerCallback(object state)
		{
			await _messageLoadLock.WaitAsync();

			try
			{
				await _messages.ScrollForward();
				ProcessGroupMeMessage(_messages.LoadedMessages);
			}
			finally
			{
				_messageLoadLock.Release();
			}
		}

		public void ProcessGroupMeMessage(IEnumerable<GroupMeMessageData> messages)
		{
			foreach (var msg in messages)
			{
				if (_processedMessageIds.Contains(msg.MessageId))
				{
					continue;
				}

				_messageProcessingLock.Wait();

				try
				{
					if (_processedMessageIds.Contains(msg.MessageId))
					{
						continue;
					}

					// SECURITY: Never use posted text directly in a HTML Page! Always HTML-Encode it!
					var processedMessage = HttpUtility.HtmlEncode(msg.Text);

					if (msg.Attachments.Any(a => a.Type == AttachmentType.Emoji))
					{
						processedMessage = GroupMeEmojiMessageRenderer.MergeMessageWithEmojiImages(processedMessage,
							(GroupMeEmojiAttachment) msg.Attachments.FirstOrDefault(att => att.Type == AttachmentType.Emoji));
					}

					msg.Text = processedMessage;

					_processedMessageIds.Add(msg.MessageId);
				}
				finally
				{
					_messageProcessingLock.Release();
				}
			}
		}

		public async Task PostMessage(int userId, string sessionId, string message)
		{
			if (userId <= 0 && string.IsNullOrEmpty(sessionId))
			{
				throw new UnauthorizedAccessException("You need to be logged in to post a message.");
			}

			var group = await GetGroup(userId, sessionId);

			try
			{
				await group.Messages.CreateMessage(Guid.NewGuid().ToString(), message);
			}
			catch (GroupMeException ex)
			{
				Exceptions.LogException(ex);
				throw;
			}
		}

		private async Task<GroupMeGroup> GetGroup(int? userId = null, string sessionId = null)
		{
			string authToken = null;

			if (userId.HasValue || !string.IsNullOrEmpty(sessionId))
			{
				authToken = AuthManager.GetAuthTokenForUserOrAnonymous(userId.HasValue ? userId.Value : -1, sessionId);

				if (string.IsNullOrEmpty(authToken))
				{
					throw new UnauthorizedAccessException("No Auth token found, you need to log into GroupMe first.");
				}
			}
			else
			{
				var settings = ModuleSettings;
				authToken = settings.MessageLoadUserAccessToken;
			}

			var groupMeGroupId = ModuleSettings.GroupMeGroupId;
			var apiAccess = AuthHelper.Value.GetWebApiAccessForAccessToken(authToken);
			var groupFactory = new GroupMeGroupFactory(apiAccess);
			return await groupFactory.GetGroup(groupMeGroupId);
		}
	}
}